import { Asset, AssetManager, assetManager, director, dragonBones, Node, Scene, SceneAsset, SpriteFrame, Texture2D, v2, Vec2 } from "cc";
import { EDITOR } from "cc/env";
import { Singleton } from "./Common";

const RES:string = 'resources';

type CompleteCall = (res:Asset|Asset[])=>void;
type ProgressCall = (finish:number,total:number,item:any)=>void;
interface LoadItem {
    file:string,
    call:(name:string,res:Asset)=>void,
}

interface WaitQueue{
    files:string|string[],
    complete?:(name:string,res:Asset)=>void,
    type?:typeof Asset,
}
export default class assetsMgr extends Singleton<assetsMgr>{
    private _bundleCache:Map<string,AssetManager.Bundle> = new Map<string,AssetManager.Bundle>();
    private _assetsCache:Map<string,Asset> = new Map<string,Asset>();
    private _waitTimer:any = null;
    private _isWait:boolean = false;
    private _waitQueue:WaitQueue[] = [];

    constructor(){
        super();
    }

    protected onDestroy(){
    }

    protected onInit(){
        this._bundleCache.set('resources',assetManager.resources);
    }
    	
    /**
     * 获取纹理使用情况
     */
     public getTextureUsage():{count:number,memory:number}{
        let count = 0, memory = 0;
        assetManager.assets.forEach((asset: Asset, key: string) => {
            if (asset instanceof Texture2D) {
                count++;
                const pixelSize = asset['_native'] === '.jpg' ? 3 : 4,
                    textureSize = (asset.width * asset.height) * pixelSize / (1024 * 1024);
                memory += textureSize;
            }
        });
        return { count, memory };
    }

    
 
    /**
     * 在编辑器环境中加载 assets 目录下的资源
     * @param path 相对路径，根目录为 assets/，注意带上文件后缀，如：eazax-sine-wave.effect
     * @param assetType 资源类型，如：effect
     * @param callback 加载完成回调
     */
     public editorload<T>(path: string, assetType: string, call?: (err: Error, result: T) => void): Promise<T> {
        return new Promise(res => {
            if (!EDITOR) {
                res(null);
                console.warn('[EditorAsset]', '该函数只在编辑器环境内有效!');
                return;
            }
            // @ts-ignore
            Editor.assetdb.queryAssets(`db://assets/${path}`, assetType, (err: Error, results: AssetInfo[]) => {
                if (err) {
                    res(null);
                    call && call(new Error('[EditorAsset] 未知错误!'), null);
                    return;
                }
                if (results.length === 0) {
                    res(null);
                    call && call(new Error('[EditorAsset] 未找到指定资源!'), null);
                    return;
                }
                if (assetManager && assetManager.loadAny) {
                    assetManager.loadAny({ uuid: results[0].uuid }, (_err: Error, _result: T) => {
                        res(_result);
                        call && call(_err, _result);
                    });
                }
            });
        });
    }

    /**
     * 异步加载资源\目录
     * resources目录下
     * res/img/aa       加载资源 resources/res/aa
     * res/img/bb/      加载目录 resources/res/bb/
     * 加载子包
     * res/aa:bundle    加载子包bundle下res/aa的文件资源
     * res/bb/:bundle   加载子包bundle下res/bb文件夹下的所有资源
     * http://... https://... 加载一个远程资源
     * @param path 文件(夹)路径
     * @param onComplete  加载结束回调
     * @param type          资源类型
     * @param onProgress    加载进度
     */
    public load(path:string,onComplete:CompleteCall,type?: typeof Asset,onProgress?:ProgressCall){
        const tmpPath:string = path.toLowerCase();
        if (tmpPath.indexOf('http') === 0){
            this._loadRemote(path,onComplete);
        } else if (tmpPath.lastIndexOf('/') === tmpPath.length - 1){
            this._loadDir(path,onComplete,type,onProgress);
        } else {
            this._loadAsset(this._checkPath(path,type),onComplete);
        }
    }

    /**
     * 同步(加载成功一个后才会加载下一个)加载一个数组资源
     * 注意不要文件和文件夹混杂，否则可能会出错
     * @param path 欲加载数组
     * @param onComplete 单个资源加载成功回调
     */
    public waitLoad(paths:string|string[],onComplete:(name:string,res:Asset)=>void,type?:typeof Asset){
        if (this._waitQueue.length > 0){
            this._waitQueue.push({files:paths,complete:onComplete,type:type});
            return ;
        }
        this._waitQueue.push({files:paths,complete:onComplete,type:type});
        if (this._isWait) return ;
        if (this._waitTimer !== null){
            clearInterval(this._waitTimer);
            this._waitTimer = null;
        }
        
        this._waitTimer = setInterval(()=>{
            if (this._isWait) return ;
            const it:WaitQueue = this._waitQueue.shift();
            if (this._waitQueue.length < 1){
                clearInterval(this._waitTimer);
                this._waitTimer = null;
            }
            this._isWait = true;
            const _load = (file:string,idx:number,all:number)=>{
                let tmpPath:string = this._checkPath(file,it.type);
                this.load(tmpPath,(assets:Asset)=>{
                    it.complete && it.complete(file,assets);
                    if (idx === all-1) this._isWait = false;
                });
            };
            if (Array.isArray(it.files)){
                it.files.forEach((path:string,idx:number)=>{
                    _load(path,idx,it.files.length);
                });
            } else {
                _load(it.files,0,1);
            }
        },10);
    }

    public async syncLoad(path:string, type?:typeof Asset):Promise<Asset | Asset[]>{
        return new Promise<Asset | Asset[]>((resolve)=>{
            this.load(path,(asset:Asset | Asset[])=>{
                resolve(asset);
            },type);
        });
    }

    public async loadScene(name:string,call:(scene:Scene)=>void){
        const subs:string[] = name.split(':');
        if (subs.length < 2){
            director.loadScene(name,(err: Error, scene: Scene)=>{
                if (err) throw new Error(err.message);
                call && call(scene);
            });
        } else {
            const _load = (bundle:AssetManager.Bundle)=>{
                bundle.loadScene(subs[0],(err:Error, sceneAsset: SceneAsset)=>{
                    if (err) throw new Error(err.message);
                    call && call(sceneAsset.scene);
                });
            };
            const _bundle:AssetManager.Bundle = this._bundleCache.get(subs[1]);
            this._isInvalid(_bundle) ? this._loadBundle(subs[1],_load) : _load(_bundle);
        }
    }

    /**
     * 移除一个子包
     * @param name 子包名
     * @returns 
     */
    public remove(name:string){
        if (name === RES){
            console.warn('resources cannot be removed.')
            return ;
        }
        const bundle:AssetManager.Bundle = this._bundleCache.get(name);
        if (this._isInvalid(bundle)) return ;
        this._bundleCache.delete(name);
        bundle.releaseAll();
        assetManager.removeBundle(bundle);
    }

    /**
     * 加载资源
     * @param path 
     * @param onComplete 
     */
    private async _loadAsset(path:string,onComplete:(res:Asset)=>void){
        const subs:string[] = path.split(':');
        const key:string = subs.length < 2 ? RES : subs[1];
        const _bundle:AssetManager.Bundle = this._bundleCache.get(key);
        const fullPath:string = `${key}:${subs[0]}`;
        if (this._assetsCache.has(fullPath)){
            const asset:Asset = this._assetsCache.get(fullPath);
            asset.addRef();
            onComplete && onComplete(asset);
            return ;
        }
        const _load = (bundle:AssetManager.Bundle)=>{
            bundle.load(subs[0],(err: Error, asset: Asset)=>{
                if (err) 
                    throw new Error(err.message);
                this._assetsCache.set(fullPath,asset);
                onComplete && onComplete(asset);
            });
        };
        if (this._isInvalid(_bundle)){
            this._loadBundle(key,(bundle:AssetManager.Bundle)=>{
                _load(bundle);
            });
        } else {
            _load(_bundle);
        }
    }

    private _checkPath(path:string,type:typeof Asset):string{
        if (type === SpriteFrame)path = `${path}/spriteFrame`;
        else if (type === Texture2D)path = `${path}/texture`;
        return path;
    }
    /**
     * 加载目录中指定类型资源
     * @param dir 
     * abc          加载assets/resources/res/abc 目录
     * res/abc:Bundle 加载子包Hall 路径下assets/.../Bundle/res/abc 目录
     * @param type 目录中加载的资源类型
     * @param onComplete 结束回调
     * @param onProgress 进度回调
     */
    private async _loadDir(dir:string,onComplete:CompleteCall,type: typeof Asset,onProgress?:ProgressCall){
        const subs:string[] = dir.split(':');
        const key:string = subs.length < 2 ? RES : subs[1];
        const _bundle:AssetManager.Bundle = this._bundleCache.get(key);
        dir = key === RES ? dir : `res/${subs[0]}`;
        const fullDir:string = `${key}/${dir}`;
        const _load = (bundle:AssetManager.Bundle)=>{
            type ? bundle.loadDir(dir,type,(finish: number, total: number, item: AssetManager.RequestItem) => {
                this._assetsCache.set(item.file,item.content);
                onProgress && onProgress(finish,total,item.content);
            }, (err: Error, assets: Array<Asset>)=>{
                if (err) throw new Error(err.message);
                onComplete && onComplete(assets);
            }) : bundle.loadDir(dir,(finish: number, total: number, item: AssetManager.RequestItem) => {
                this._assetsCache.set(item.file,item.content);
                onProgress && onProgress(finish,total,item.content);
            }, (err: Error, assets: Array<Asset>)=>{
                if (err) throw new Error(err.message);
                onComplete && onComplete(assets);
            });
        };
        if (this._isInvalid(_bundle)){
            this._loadBundle(key,(bundle:AssetManager.Bundle)=>{
                _load(bundle);
            });
        } else {
            _load(_bundle);
        }
    }

    /**
     * 加载远程资源
     * @param url 
     * @param cb 
     * @returns 
     */
    private async _loadRemote(url:string,onComplete:CompleteCall){
        if (this._assetsCache.has(url)){
            onComplete && onComplete(this._assetsCache.get(url));
            return ;
        }
        if (url.indexOf('http://') !== -1 && url.indexOf('https://') !== -1){
            console.error(`AssetsManger.loadRemote(${url}) error.`);
            return ;
        } else {
            assetManager.loadRemote(url, (e:Error, res:Asset) => {
                if (e || !res){
                    console.error(`AssetsManger.loadRemote(${url}) is error.`);
                    return ;
                }
                this._assetsCache.set(url,res);
                onComplete && onComplete(res);
            })
        }
    }

    private _find(name:string):Asset{
        if (this._assetsCache.has(name)){
            return this._assetsCache.get(name);
        }
        return null;
    }

    // 对象是否有效
    private _isInvalid<T>(obj:T):boolean{
        if (obj === undefined || obj === null) return true;
        else if (typeof obj === 'number' && isNaN(obj)) return true;
        else if (typeof obj === 'string' && obj.length < 1) return true;
        return  false;
    };

    private async _loadBundle(name:string,call:(bundle:AssetManager.Bundle)=>void){
        assetManager.loadBundle(name,(err:Error,bundle:AssetManager.Bundle)=>{
            if (err){
                throw new Error(err.message);
            }
            this._bundleCache.set(name,bundle);
            call && call(bundle);
        });
    }
    /**
     * 
     * @param displayLayout 动画展示节点
     * @param path 动画目录 eg: effects/zhadan:room
     * @param armature 当前的 Armature 名称
     * @param animName 需要播放的动画名 b
     * @param playTimes 播放次数 -1 为使用配置文件中的次数 0 为无限循环播放 > 0 为动画的重复次数
     * @param call 结束回调
     */
    public displayDragonBones(displayLayout:Node, path:string,armature:string, animName: string, playTimes: number = 1, call:()=>void = null){
        console.log(`AssetsMgr.displayDragonBones0(${displayLayout.name},${path},${armature},${animName},${playTimes})`);
        
        this._loadDir(path,(assets:Asset[])=>{
            if (assets.length <= 0){
                call && call();
                return;
            }

            displayLayout.getComponent(dragonBones.ArmatureDisplay) && displayLayout.removeComponent(dragonBones.ArmatureDisplay);
            const ArmatureDisplay:dragonBones.ArmatureDisplay = displayLayout.addComponent(dragonBones.ArmatureDisplay); 

            for (let it of assets){
                if (it instanceof dragonBones.DragonBonesAsset) ArmatureDisplay.dragonAsset  = it;
                if (it instanceof dragonBones.DragonBonesAtlasAsset) ArmatureDisplay.dragonAtlasAsset = it;
            }

            if (ArmatureDisplay.dragonAsset && ArmatureDisplay.dragonAtlasAsset){
                console.log(`AssetsMgr.displayDragonBones1(${displayLayout.name},${path},${armature},${animName},${playTimes})`);
                
                ArmatureDisplay.armatureName = armature;// 一定要设置armatureName
                ArmatureDisplay.playAnimation(animName, playTimes);// 播放动画（同上）
                const playEndCall = (event: Event)=>{
                    ArmatureDisplay.removeEventListener(dragonBones.EventObject.COMPLETE,playEndCall,this);
                    displayLayout.removeComponent(dragonBones.ArmatureDisplay);
                    call && call();
                };
                // 监听
                ArmatureDisplay.addEventListener(dragonBones.EventObject.COMPLETE, playEndCall, this);
            } else {
                console.log(`AssetsMgr.displayDragonBones2(ERROR)`);
                call && call();
            }
        },null);
    }

    public playDragonBones(root:Node,path:string,armature:string,palyName:string,playTimes:number=1,start?:()=>void,end?:()=>void,pos:Vec2=v2(0,0)){
        if (!(root instanceof Node)){
            console.error(`AssetsMgr.playDragonBones(root = null)`);
            return ;
        }
        
        let dragonAsset,atlasAsset;
        this._loadDir(path,(assets:Asset[])=>{
            for (let it of assets){
                if (it instanceof dragonBones.DragonBonesAsset) dragonAsset  = it;
                if (it instanceof dragonBones.DragonBonesAtlasAsset) atlasAsset = it;
            }

            if (this._isInvalid(dragonAsset) || this._isInvalid(atlasAsset)){
                console.error(`AssetsMgr.playDragonBones(DragonBones(${path}) is invalid.)`);
                start && start();
                end && end();
                return ;
            }
            let _node = new Node(path);
            _node.parent = root;
            _node.setPosition(pos.x,pos.y);
            const DB = _node.addComponent<dragonBones.ArmatureDisplay>(dragonBones.ArmatureDisplay);
            DB.dragonAsset = dragonAsset;
            DB.dragonAtlasAsset = atlasAsset;
            DB.armatureName = armature;
            DB.animationName = palyName;
            DB.playTimes = playTimes;
            DB.playAnimation(palyName, playTimes);

            // 监听
            start && DB.addEventListener(dragonBones.EventObject.START, start, this);
            // 监听
            end && DB.addEventListener(dragonBones.EventObject.COMPLETE, ()=>{
                root.removeAllChildren();
                end();
            }, this);
        },null);
    }
}